#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using UTable.Input;
using UTable.Input.MultiTouch;
using UTable.Objects;
using UTable.Objects.Controls;
using System.Windows.Data;
using System.Globalization;
using UTable.Objects.Handlers;
using System.Windows.Media.Animation;
using System.ComponentModel;

namespace UTable.Objects.Controls
{

	/// <summary>
	/// A control which is the abstraction of drawer
	/// e.g. support pulling to open, pushing to close
	/// </summary>
	public class UDrawer : UContentControl
	{

		#region Private Fiedls

		private UDrawerHandle _handler;

		private Grid _container;

        private int _fullOpenTime = 800;  // milliseconds

		#endregion

		#region Constructors

		static UDrawer()
		{
			DefaultStyleKeyProperty.OverrideMetadata(typeof(UDrawer), new FrameworkPropertyMetadata(typeof(UDrawer)));
		}

		/// <summary>
		/// UDrawer Constructor
		/// </summary>
		public UDrawer()
		{
			this.ClipToBounds = true;
		}

		#endregion

		#region Private Methods

		private void OnIsOpenChanged()
		{
			if (IsOpen)
			{
				this.ClipToBounds = false;
				DrawerOffset = ContentLength;
			}
			else
			{
				this.ClipToBounds = true;
				DrawerOffset = 0;
			}
		}

		private static void IsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			(d as UDrawer).OnIsOpenChanged();
		}

		#endregion

		#region Handle Input

		private double _dragStartOffset;

		private void InitializeHandlerInputPart()
		{
            SingleFingerGestureHandler handler = new SingleFingerGestureHandler(_handler, this);
            handler.GestureDetected += new SingleFingerGestureEventHandler(Handler_GestureDetected);
            _handler.MultiTouchProcessor.Handlers.Add(handler);
		}

        private void Handler_GestureDetected(SingleFingerGestureHandler sender, SingleFingerGestureEventArgs args)
        {
            switch (args.Type)
            {
                case SingleFingerGestureType.Tap:
                    OnHandlerTapped(args as SingleFingerGestureTapEventArgs);
                    break;
                case SingleFingerGestureType.DragStarted:
                    OnHandlerDragStarted(args as SingleFingerGestureDragEventArgs);
                    break;
                case SingleFingerGestureType.Dragging:
                    OnHandlerDragging(args as SingleFingerGestureDragEventArgs);
                    break;
                case SingleFingerGestureType.DragEnded:
                    OnHandlerDragEnded(args as SingleFingerGestureDragEventArgs);
                    break;
            }
        }

        /// <summary>
        /// Position relative to the UDrawer
        /// </summary>
        protected virtual void OnHandlerTapped(SingleFingerGestureTapEventArgs args)
        {
            if (this.IsOpen)
            {
                AnimateToClose();
            }
            else
            {
                AnimateToOpen();
            }
        }

        protected virtual void OnHandlerDragStarted(SingleFingerGestureDragEventArgs args)
        {
            _dragStartOffset = this.DrawerOffset;
			this.ClipToBounds = true;
        }

        /// <summary>
        /// Position relative to the UDrawer
        /// </summary>
        protected virtual void OnHandlerDragging(SingleFingerGestureDragEventArgs args)
        {
            if (this.OpenDirection == ExpandDirection.Up)
            {
                double offset = args.OriginalFingerPosition.Y - args.FingerPosition.Y + _dragStartOffset;
                if (offset > ContentLength)
                    offset = ContentLength;
                else if (offset < 0)
                    offset = 0;
                DrawerOffset = offset;
            }
			else if (this.OpenDirection == ExpandDirection.Down)
			{
				double offset = args.FingerPosition.Y - args.OriginalFingerPosition.Y + _dragStartOffset;
				if (offset > ContentLength)
					offset = ContentLength;
				else if (offset < 0)
					offset = 0;
				DrawerOffset = offset;
			}
			else if (this.OpenDirection == ExpandDirection.Left)
			{
				double offset = args.OriginalFingerPosition.X - args.FingerPosition.X + _dragStartOffset;
				if (offset > ContentLength)
					offset = ContentLength;
				else if (offset < 0)
					offset = 0;
				DrawerOffset = offset;
			}
			else if (this.OpenDirection == ExpandDirection.Right)
			{
				double offset = args.FingerPosition.X - args.OriginalFingerPosition.X + _dragStartOffset;
				if (offset > ContentLength)
					offset = ContentLength;
				else if (offset < 0)
					offset = 0;
				DrawerOffset = offset;
			}
        }

        /// <summary>
        /// Position relative to the UDrawer
        /// </summary>
        protected virtual void OnHandlerDragEnded(SingleFingerGestureDragEventArgs args)
        {
			if (this.DrawerOffset <= 0)
			{
                Close();
				return;
			}
			else if (this.DrawerOffset >= ContentLength)
			{
                Open();
				return;
			}

            // if (this.OpenDirection == ExpandDirection.Up)
            {
                if (this.DrawerOffset < this.ContentLength / 2)
                {
                    // animate to close
                    AnimateToClose(TimeSpan.FromMilliseconds(_fullOpenTime * this.DrawerOffset / ContentLength));
                }
                else
                {
                    // animate to open
                    AnimateToOpen(TimeSpan.FromMilliseconds(_fullOpenTime * (ContentLength - this.DrawerOffset) / ContentLength));
                }
            }
        }

		#endregion

		#region Public Methods

        public void AnimateToOpen()
        {
            AnimateToOpen(TimeSpan.FromMilliseconds(_fullOpenTime));
        }

        public void AnimateToClose()
        {
            AnimateToClose(TimeSpan.FromMilliseconds(_fullOpenTime));
        }

		public void AnimateToOpen(TimeSpan duration)
		{
            AnimationTimeline animation = AnimationUtility.CreateDoubleAnimation(ContentLength, duration);
            animation.FillBehavior = FillBehavior.Stop;
            animation.Completed += new EventHandler(delegate(Object obj, EventArgs args)
            {
                Open();
            });

            this.BeginAnimation(UDrawer.DrawerOffsetProperty, animation);
		}

        public void AnimateToClose(TimeSpan duration)
		{
			this.ClipToBounds = true;
            AnimationTimeline animation = AnimationUtility.CreateDoubleAnimation(0, duration);
            animation.FillBehavior = FillBehavior.Stop;
            animation.Completed += new EventHandler(delegate(Object obj, EventArgs args)
            {
                Close();
            });

            this.BeginAnimation(UDrawer.DrawerOffsetProperty, animation);
		}

		public void Open()
		{
			this.IsOpen = true;
            DrawerOffset = ContentLength;
		}

		public void Close()
		{
			this.IsOpen = false;
            DrawerOffset = 0;
		}

		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			_handler = this.GetTemplateChild("PART_Handle") as UDrawerHandle;
            InitializeHandlerInputPart();

			// set the binding
			_container = this.GetTemplateChild("PART_Container") as Grid;
			MultiBinding binding = new MultiBinding();
			Binding bind1 = new Binding("Width");
			bind1.Source = this;
			Binding bind2 = new Binding("Height");
			bind2.Source = this;
			Binding bind3 = new Binding("HandleLength");
			bind3.Source = this;
			Binding bind4 = new Binding("DrawerOffset");
			bind4.Source = this;
			Binding bind5 = new Binding("OpenDirection");
			bind5.Source = this;

			binding.Bindings.Add(bind1);
			binding.Bindings.Add(bind2);
			binding.Bindings.Add(bind3);
			binding.Bindings.Add(bind4);
			binding.Bindings.Add(bind5);
			binding.Converter = new DrawerRootMaginConverter();
			_container.SetBinding(FrameworkElement.MarginProperty, binding);
			if (IsOpen)
				Open();
			else
				Close();
		}

		#endregion

		#region Properties

		public static readonly DependencyProperty HandleLengthProperty =
			DependencyProperty.Register("HandleLength", typeof(double), typeof(UDrawer), new UIPropertyMetadata(50.0));

		public static readonly DependencyProperty OpenDirectionProperty =
			DependencyProperty.Register("OpenDirection", typeof(ExpandDirection), typeof(UDrawer), new UIPropertyMetadata(ExpandDirection.Up));

		public static readonly DependencyProperty IsOpenProperty =
			DependencyProperty.Register("IsOpen", typeof(Boolean), typeof(UDrawer), new UIPropertyMetadata(false, new PropertyChangedCallback(IsOpenChanged)));

		public static readonly DependencyProperty DrawerNameProperty =
			DependencyProperty.Register("DrawerName", typeof(String), typeof(UDrawer), new UIPropertyMetadata("UDrawer"));

		public static readonly DependencyProperty DrawerOffsetProperty =
			DependencyProperty.Register("DrawerOffset", typeof(double), typeof(UDrawer), new UIPropertyMetadata(0.0));

        /// <summary>
        /// The Size of the handle
        /// </summary>
        public double HandleLength
        {
            get { return (double)GetValue(HandleLengthProperty); }
            set { SetValue(HandleLengthProperty, value); }
        }

		/// <summary>
		/// Gets or sets whether the drawer is open
		/// This is a dependency property
		/// </summary>
		/// <return>
		/// The default value is false
		/// </return>
		public Boolean IsOpen
		{
			get { return (Boolean)GetValue(IsOpenProperty); }
			set { SetValue(IsOpenProperty, value); }
		}

		/// <summary>
		/// Gets or sets how tab headers align relative to the tab content. 
		/// This is a dependency property.
		/// </summary>
		/// <return>
		/// The alignment of tab headers relative to tab content. The default is System.Windows.Controls.Dock.Top
		/// </return>
		public ExpandDirection OpenDirection
		{
			get { return (ExpandDirection)GetValue(OpenDirectionProperty); }
			set { SetValue(OpenDirectionProperty, value); }
		}

		/// <summary>
		/// The name of the drawer, which will be shown on the handle of the drawer
		/// </summary>
		/// <return>
		/// The name of the drawer. The default is "UDrawer"
		/// </return>
		public String DrawerName
		{
			get { return (String)GetValue(DrawerNameProperty); }
			set { SetValue(DrawerNameProperty, value); }
		}

		/// <summary>
		/// The current offset of the drawer.
		/// O is close, ContentLength is open
		/// </summary>
		public double DrawerOffset
		{
			get { return (double)GetValue(DrawerOffsetProperty); }
			set { SetValue(DrawerOffsetProperty, value); }
		}

		public double ContentLength
		{
			get 
            {
				if (this.OpenDirection == ExpandDirection.Up || this.OpenDirection == ExpandDirection.Down)
					return this.Height - HandleLength;
				else if (this.OpenDirection == ExpandDirection.Left || this.OpenDirection == ExpandDirection.Right)
					return this.Width - HandleLength;
                    throw new InvalidEnumArgumentException();
            }
		}

        /// <summary>
        /// The time to animate from close to open, measured in milliseconds
        /// </summary>
        public int FullOpenTime
        {
            get { return _fullOpenTime; }
            set { _fullOpenTime = value; }
        }

		#endregion
	}

	public class DrawerRootMaginConverter : IMultiValueConverter
	{
		#region IMultiValueConverter Members

		public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
		{
			double drawerWidth = (double)values[0];
			double drawerHeight = (double)values[1];
			double handleLength = (double)values[2];
			double offset = (double)values[3];
			ExpandDirection direction = (ExpandDirection)values[4];

			if (direction == ExpandDirection.Up)
			{
				double margin = drawerHeight - handleLength - offset;
				return new Thickness(0, margin, 0, -margin);
			}
			else if (direction == ExpandDirection.Left)
			{
				double margin = drawerWidth - handleLength - offset;
				return new Thickness(margin, 0, -margin, 0);
			}
			else if (direction == ExpandDirection.Right)
			{
				double margin = drawerWidth - handleLength - offset;
				return new Thickness(-margin, 0, margin, 0);
			}
			else if (direction == ExpandDirection.Down)
			{
				double margin = drawerHeight - handleLength - offset;
				return new Thickness(0, -margin, 0, margin);
			}
			else
				throw new InvalidEnumArgumentException();
		}

		public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
		{
			throw new NotImplementedException();
		}

		#endregion
	}
}
